#include "clib_base64.h"

static const u08_t base64_en[] = {

    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',

    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',

    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 

    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',

    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 

    'o', 'p', 'q', 'r', 's', 't', 'u', 'v',

    'w', 'x', 'y', 'z', '0', '1', '2', '3', 

    '4', '5', '6', '7', '8', '9', '+', '/' 
};

static const u08_t base64_de[] = {
	/* nul, soh, stx, etx, eot, enq, ack, bel, */
	255, 255, 255, 255, 255, 255, 255, 255,

	/*  bs,  ht,  nl,  vt,  np,  cr,  so,  si, */
	255, 255, 255, 255, 255, 255, 255, 255,

	/* dle, dc1, dc2, dc3, dc4, nak, syn, etb, */
	255, 255, 255, 255, 255, 255, 255, 255,

	/* can,  em, sub, esc,  fs,  gs,  rs,  us, */
	255, 255, 255, 255, 255, 255, 255, 255,

	/*  sp, '!', '"', '#', '$', '%', '&', ''', */
	255, 255, 255, 255, 255, 255, 255, 255,

	/* '(', ')', '*', '+', ',', '-', '.', '/', */
	255, 255, 255,  62, 255, 255, 255,  63,

	/* '0', '1', '2', '3', '4', '5', '6', '7', */
	52,  53,  54,  55,  56,  57,  58,  59,

	/* '8', '9', ':', ';', '<', '=', '>', '?', */
	60,  61, 255, 255, 255, 255, 255, 255,

    /*'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', */
	255,   0,   1,  2,   3,   4,   5,    6,

	/* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', */
	7,   8,   9,  10,  11,  12,  13,  14,

	/* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', */
	15,  16,  17,  18,  19,  20,  21,  22,

	/* 'X', 'Y', 'Z', '[', '\', ']', '^', '_', */
	23,  24,  25, 255, 255, 255, 255, 255,

	/* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', */
	255,  26,  27,  28,  29,  30,  31,  32,

	/* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', */
	33,  34,  35,  36,  37,  38,  39,  40,

	/* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', */
	41,  42,  43,  44,  45,  46,  47,  48,

	/* 'x', 'y', 'z', '{', '|', '}', '~', del, */
	49,  50,  51, 255, 255, 255, 255, 255
};

i32_t clib_base64_encode(u08_t *in_buf, i32_t in_len, u08_t *out_buf, i32_t out_max)
{
    u08_t a, b, c;

    i32_t idx = 0;
    i32_t en_len = 0;
    i32_t group  = in_len / 3;
    i32_t mod    = in_len % 3;
    
    if(CLIB_BASE64_ENCODE_LEN(in_len) > out_max) {
        return 0;
    }

    for(i32_t i = 0; i < group; i++) {
        a = in_buf[idx];
        b = in_buf[idx + 1];
        c = in_buf[idx + 2];

        out_buf[en_len++] = base64_en[ (a >> 2) & 0x3F];
        out_buf[en_len++] = base64_en[((a & 0x03) << 4) | ((b >> 4) & 0x0F)];
        out_buf[en_len++] = base64_en[((b & 0x0F) << 2) | ((c >> 6) & 0x03)];
        out_buf[en_len++] = base64_en[c & 0x3F ];

        idx += 3;
    }

    if(mod == 1) {
        a = in_buf[idx];
        out_buf[en_len++] = base64_en[ (a >> 2) & 0x3F];
        out_buf[en_len++] = base64_en[((a & 0x03) << 4)];
        out_buf[en_len++] = '=';
        out_buf[en_len++] = '=';
    } else if (mod == 2) {
        a = in_buf[idx];
        b = in_buf[idx + 1];
        out_buf[en_len++] = base64_en[ (a >> 2) & 0x3F];
        out_buf[en_len++] = base64_en[((a & 0x03) << 4)];
        out_buf[en_len++] = base64_en[((b & 0x0F) << 2)];
        out_buf[en_len++] = '=';
    } 

    return en_len;
}

i32_t clib_base64_decode(u08_t *in_buf, i32_t in_len, u08_t *out_buf, i32_t out_max)
{
    i32_t group;
    i32_t mod;
    u08_t a, b, c, d;
    i32_t idx = 0;
    
    if(in_len & 3) {
        return 0;
    }

    if(CLIB_BASE64_DECODE_LEN(in_len) > out_max) {
        return 0;
    }

    while( in_len && in_buf[in_len -1] == '=') {
        in_len--;
    }

    group = in_len / 4;
    mod   = in_len % 4;

    for(i32_t i = 0; i < group; i++) {
        a = base64_de [*in_buf++];
        b = base64_de [*in_buf++];
        c = base64_de [*in_buf++];
        d = base64_de [*in_buf++];

        if ((a | b | c | d) == 0xFF) {
            return 0;
        }

        out_buf[idx++] = ((a << 2) | ((b & 0x30) >> 4));
        out_buf[idx++] = (((b & 0xf) << 4 ) | ((c & 0x3c) >> 2));
        out_buf[idx++] = (((c & 0x3) << 6) | d);
    }

    if(mod == 2) {
        a =base64_de [*in_buf++];
        b =base64_de [*in_buf++];
        out_buf[idx++] = ((a << 2) | ((b & 0x30) >> 4));
    } else if(mod == 3) {
        a =base64_de [*in_buf++];
        b =base64_de [*in_buf++];
        c =base64_de [*in_buf++];
        out_buf[idx++] = ((a << 2) | ((b & 0x30) >> 4));
        out_buf[idx++] = (((b & 0xf) << 4 ) | ((c & 0x3c) >> 2));
    }

    return idx;
}